`include "define.v"

module nic #(
	parameter VC_NUM_PER_PORT 		=	2,
	parameter PYLD_WIDTH 			=	32,
	parameter BUFFER_NUM_PER_VC	=	16,
	parameter MAX_PCK_SIZE			=	256,	//maximum packet size in word
	parameter FLIT_TYPE_WIDTH		=	2,
	parameter PORT_NUM				=	5,
	parameter X_NODE_NUM				=	4,
	parameter Y_NODE_NUM				=	3,
	parameter SW_X_ADDR				=	2,
	parameter SW_Y_ADDR				=	1,
	parameter FIFO_FULL_SIG_WIDTH	=	VC_NUM_PER_PORT*2,
	parameter VC_ID_WIDTH			=	VC_NUM_PER_PORT,
	parameter FLIT_WIDTH				=	PYLD_WIDTH+FLIT_TYPE_WIDTH+VC_ID_WIDTH,
	parameter M_ADDR_SIZE			=	21,
	parameter S_ADDR_SIZE			=	2
	//parameter ADDR_SIZE				=	log2(VC_NUM_PER_PORT)
	)
	(
	
	input 												reset,
	input													clk,
	
	
	// NOC interfaces
	output	[PYLD_WIDTH+VC_NUM_PER_PORT+FLIT_TYPE_WIDTH-1	:0] 	flit_out,     
	output 	reg    			   					flit_out_wr,   
	input 	[VC_NUM_PER_PORT*2-1			:0]	flit_out_vc_full,//stupid qsys cannot accept FIFO_FULL_SIG_WIDTH as size
	
	input		[PYLD_WIDTH+VC_NUM_PER_PORT+FLIT_TYPE_WIDTH-1	:0] 	flit_in,     // Data in
	input 	    			   						flit_in_wr,   // Write enable
	output reg	[VC_NUM_PER_PORT-1			:0]	credit_out,								
	
	//avalon slave interface signals
	input									s_chipselect, 
	input									s_write,
	input									s_read,
	input 	[S_ADDR_SIZE-1	:0]	s_address,
	input 	[31				:0] 	s_writedata, 
	output 	[31				:0] 	s_readdata,
	output 								s_waitrequest,
	
	
	//avalon master interface signals
	output								m_chipselect, 
	output								m_write,
	output								m_read,
	output 	[M_ADDR_SIZE-1	:0]	m_address,
	output 	[31				:0] 	m_writedata, 
	input 	[31				:0] 	m_readdata,
	input 								m_waitrequest
); 
 
`LOG2

	localparam	COUNTER_WIDTH		=	M_ADDR_SIZE-2;
	localparam	PTR_WIDTH			=	19;
	localparam	PCK_SIZE_WIDTH		=	13;
	localparam	HDR_FLIT				=	2'b10;
	localparam	BDY_FLIT				=	2'b00;
	localparam	TAIL_FLIT			=	2'b01;
	localparam	SLAVE_RD_PCK_ADDR	=	0;
	localparam	SLAVE_WR_PCK_ADDR	=	1;
	localparam	SLAVE_STATUS_ADDR	=	2;
	
	localparam	NUMBER_OF_STATUS	=	6;
	localparam  IDEAL					=	1;
	localparam	READ_MEM_PCK_HDR	=	2;
	localparam 	ASSIGN_PORT_VC		=	4;
	localparam	WR_ON_FIFO			=	8;
	localparam	WR_ON_RAM			=	16;
	localparam	PROG_WR				=	32;
	
	
	
	
	localparam X_NODE_NUM_WIDTH	=	log2(X_NODE_NUM);
	localparam Y_NODE_NUM_WIDTH	=	log2(Y_NODE_NUM);
	localparam PORT_SEL_WIDTH		=	PORT_NUM-1;
	
	
	
		
	reg [NUMBER_OF_STATUS-1	:	0]		ps,ns;
	reg [COUNTER_WIDTH-1		:	0]		counter,counter_next;
	reg 										counter_reset;
	reg 										counter_increase;
	
	// memory mapped registers
	reg [31						:	0]		wr_mem_ptr_reg;
	reg [31						:  0]		rd_mem_ptr_reg;
	wire [31						:	0]		status_reg;
	wire[31						:	0]		m_pyld;
	
	reg [PTR_WIDTH-1			:	0]		memory_ptr,memory_ptr_next;
	reg [PCK_SIZE_WIDTH-1	:	0]		pck_size,pck_size_next;
	wire										pck_eq_counter;

	reg 										wr_done_next, wr_done;
	reg										rd_done_next, rd_done;
	reg										rd_no_pck_err_next,	rd_no_pck_err;
	reg										rd_ovr_size_err_next, rd_ovr_size_err;
	reg										wr_mem_en_next,wr_mem_en;
	reg										rd_mem_en;
	
	
	reg [VC_NUM_PER_PORT-1	:	0]		cand_wr_vc,cand_wr_vc_next;
	reg										cand_wr_vc_en;
	wire										cand_wr_vc_full;
	reg [VC_NUM_PER_PORT-1	:	0]		cand_rd_vc,cand_rd_vc_next;
	wire 										no_rd_vc_is_cand;
	reg										cand_rd_vc_en,cand_rd_vc_rst;
	wire										cand_rd_vc_not_empty;
	reg										any_vc_has_data;
	
		
	
	wire [VC_NUM_PER_PORT-1	:	0]		rd_vc_arbiter_in ,rd_vc_arbiter_out; 
	reg       	   						ififo_rd_en; 
	
	
	
	wire										all_vcs_full;
	reg 										mem_wr;
	reg										fifo_wr;
	reg [FLIT_TYPE_WIDTH-1	:	0]		wr_flit_type;
	
	
	
	
	wire 	[FLIT_WIDTH-1			:0]	ififo_dout;   
	wire	[VC_NUM_PER_PORT-1	:0]	ififo_vc_not_empty;
	wire 										flit_in_hdr_flg;//if set flit is header
	wire										ififo_hdr_flg;
	wire 										ififo_tail_flg;//if set flit is tail
	wire 	[VC_NUM_PER_PORT-1	:0]	flit_in_vc_num;
	
	wire [X_NODE_NUM_WIDTH-1: 0]		dest_x_addr;
	wire [Y_NODE_NUM_WIDTH-1: 0]		dest_y_addr;
	wire [PORT_SEL_WIDTH		: 0]		port_sel;
	reg  [PORT_SEL_WIDTH		: 0]		port_sel_reg,port_sel_reg_next;
	reg										port_sel_en,port_sel_en_del;
	reg										flit_out_wr_next;
	reg										prog_mode_en;
	wire										prog_mode_en_next;
	wire [`PROG_SEQ_WIDTH-1	: 0]		prog_hdr_seq;
	
	assign	all_vcs_full  				=	& flit_out_vc_full[VC_NUM_PER_PORT-1	:	0];
	assign	cand_wr_vc_full			=	| (flit_out_vc_full [VC_NUM_PER_PORT-1	:	0] & cand_wr_vc);
			
	
	assign 	no_rd_vc_is_cand			=	~(| cand_rd_vc);
	assign 	rd_vc_arbiter_in			=	(cand_rd_vc_en)?  ififo_vc_not_empty : {VC_NUM_PER_PORT{1'b0}} ;
   assign 	cand_rd_vc_not_empty		=	|(ififo_vc_not_empty & cand_rd_vc) ;
	
	
	
	assign 	m_chipselect				=	wr_mem_en | rd_mem_en;
	assign 	m_write						=	wr_mem_en;
	assign	m_read						=	rd_mem_en;
	
	assign	m_writedata					=	{ififo_dout[31:0]};
	
	assign	flit_out						=	{wr_flit_type,cand_wr_vc,m_pyld};
	
	assign 	s_waitrequest				=	s_write & (ps!= IDEAL ) & (s_address==SLAVE_RD_PCK_ADDR | s_address==SLAVE_WR_PCK_ADDR );	
	assign	dest_x_addr					=	m_readdata[32-PORT_NUM-1						:		32-PORT_NUM-X_NODE_NUM_WIDTH ];
	assign	dest_y_addr					=	m_readdata[32-PORT_NUM-X_NODE_NUM_WIDTH-1	:		32-PORT_NUM-X_NODE_NUM_WIDTH-Y_NODE_NUM_WIDTH ];
	assign	m_pyld						=	(port_sel_en_del	)? {port_sel_reg,m_readdata[32-PORT_NUM-1:	0]}	:	m_readdata;
	
	
	assign flit_in_hdr_flg				=	flit_in	 	[`FLIT_HDR_FLG_LOC	];
	assign prog_hdr_seq 					=  flit_in 		[`PROG_SEQ_LOC			];
	assign ififo_tail_flg				=	ififo_dout	[`FLIT_TAIL_FLAG_LOC	];
	assign ififo_hdr_flg					=	ififo_dout	[`FLIT_HDR_FLG_LOC	];
	assign flit_in_vc_num				=  flit_in	 	[`FLIT_IN_VC_LOC		];
	assign pck_eq_counter				=  counter == pck_size;
	
	//status register
	assign	status_reg					=	{all_vcs_full,ififo_vc_not_empty,rd_no_pck_err,rd_ovr_size_err,rd_done,wr_done};
	assign 	s_readdata					=   status_reg;
	assign	prog_mode_en_next 		= 	flit_in_hdr_flg & (prog_hdr_seq == `PROG_SEQ_NUM);
	generate 
		if(M_ADDR_SIZE> PTR_WIDTH) 		assign	m_address					=	memory_ptr+{counter,2'b00};
		else 										assign	m_address					=	memory_ptr[M_ADDR_SIZE-1	:0 ] + {counter,2'b00};
	endgenerate																					
						
						
	
	//status
	
	
	
	// avalon slave addr 
	
	
	
	
	
	always@(posedge clk)begin
		if(reset)begin
			ps						<=	IDEAL;
			memory_ptr			<=	{PTR_WIDTH{1'b0}};
			pck_size				<=	{PCK_SIZE_WIDTH{1'b0}};
			counter				<=	{COUNTER_WIDTH{1'b0}};
			cand_wr_vc			<=	{VC_NUM_PER_PORT{1'b0}};
			cand_rd_vc			<=	{VC_NUM_PER_PORT{1'b0}};
			port_sel_reg		<= {PORT_NUM{1'b0}};
			wr_done				<=	1'b0;
			rd_done				<=	1'b0;
			rd_no_pck_err		<=	1'b0;
			rd_ovr_size_err	<= 1'b0;
			wr_mem_en			<= 1'b0;
			port_sel_en_del	<=	1'b0;
			flit_out_wr			<= 1'b0;
			any_vc_has_data	<= 1'b0;
			prog_mode_en		<= 1'b0;
			
		end else begin //if reset
			ps						<=	ns;
			memory_ptr			<=	memory_ptr_next;
			pck_size				<=	pck_size_next;
			counter				<=	counter_next;
			cand_wr_vc			<=	cand_wr_vc_next;
			cand_rd_vc			<=	cand_rd_vc_next;
			port_sel_reg		<= port_sel_reg_next;
			wr_done				<=	wr_done_next;
			rd_done				<=	rd_done_next;
			rd_no_pck_err		<=	rd_no_pck_err_next;
			rd_ovr_size_err	<= rd_ovr_size_err_next;
			wr_mem_en			<= wr_mem_en_next;
			port_sel_en_del	<= port_sel_en;
			flit_out_wr			<= flit_out_wr_next;
			any_vc_has_data	<= | ififo_vc_not_empty;
			prog_mode_en		<= prog_mode_en_next;
		end//els reset
	end//always
	
	
	always@(*)begin
	counter_next 		= counter;	
	cand_wr_vc_next	= cand_wr_vc;
	cand_rd_vc_next	= cand_rd_vc;
	port_sel_reg_next	= port_sel_reg;
	
	
		if			(counter_reset)				counter_next	=	{COUNTER_WIDTH{1'b0}};
		else if	(counter_increase) 			counter_next	=  counter +1'b1;
				
		if(cand_wr_vc_en) cand_wr_vc_next = flit_out_vc_full[FIFO_FULL_SIG_WIDTH-1	:	VC_NUM_PER_PORT];
		
		if	(cand_rd_vc_rst) 			cand_rd_vc_next =	{VC_NUM_PER_PORT{1'b0}};
		else if(cand_rd_vc_en)		cand_rd_vc_next =	rd_vc_arbiter_out;
		
		if(port_sel_en)	port_sel_reg_next	= port_sel;
		
	
		
	end//always
	
	
	
	always@(*) begin
		ns							= ps;
		counter_reset 			= 1'b0;
	   counter_increase 		= 1'b0;
		cand_rd_vc_rst			= 1'b0;
		cand_rd_vc_en			= 1'b0;
		cand_rd_vc_rst			= 1'b0;
		ififo_rd_en				= 1'b0;  
		wr_mem_en_next			= 1'b0;
		credit_out				= {VC_NUM_PER_PORT{1'b0}};
		rd_mem_en				= 1'b0;
		flit_out_wr_next		= 1'b0;
		cand_wr_vc_en			= 1'b0;
		port_sel_en				= 1'b0;
		wr_done_next			= wr_done;
		rd_done_next			= rd_done;
		rd_no_pck_err_next	= rd_no_pck_err;
		rd_ovr_size_err_next	= rd_ovr_size_err;
		memory_ptr_next		= memory_ptr;
		pck_size_next			= pck_size;
		wr_flit_type			= BDY_FLIT;	
	
		case(ps)
			
			IDEAL	:	begin 
				counter_reset =1;
				cand_rd_vc_en	=	(no_rd_vc_is_cand)? 1'b1	:	1'b0;
				if	(prog_mode_en)	begin
					ns						=	PROG_WR;
					memory_ptr_next	=	{PTR_WIDTH{1'b0}};
					ififo_rd_en			= 	1'b1;
					credit_out			=	cand_rd_vc;
				end
			   if(s_chipselect & 	s_write )	begin 
					{pck_size_next,memory_ptr_next}	= s_writedata;
					case (s_address) 
						SLAVE_RD_PCK_ADDR:	begin	
														rd_done_next		= 1'b0;
														rd_ovr_size_err_next=1'b0;
														if(any_vc_has_data)	begin 
																ns=	WR_ON_RAM;
																rd_no_pck_err_next= 1'b0;
																ififo_rd_en			= 1'b1; 
																credit_out			=	cand_rd_vc;
																wr_mem_en_next		= 1'b1;
																
														end else	begin
																ns=	IDEAL;
																rd_no_pck_err_next= 1'b1;
														end
													end   //SLAVE_RD_PCK_ADDR:
						SLAVE_WR_PCK_ADDR:	begin 			
														ns					= READ_MEM_PCK_HDR;
														wr_done_next	=1'b0;
														
													end	//SLAVE_WR_PCK_ADDR
						default:						ns=	IDEAL;
					endcase
				end
			end
			
			READ_MEM_PCK_HDR:	begin
				ns						=	ASSIGN_PORT_VC;
				rd_mem_en			= 1'b1;
			end//READ_MEM_PCK_HDR
			
			ASSIGN_PORT_VC:	begin 
				if(~all_vcs_full)	begin
					ns= WR_ON_FIFO;
					counter_increase	=	1'b1;
					cand_wr_vc_en		=	1'b1;
					port_sel_en			=	1'b1;
					flit_out_wr_next	= 	1'b1;
				end
			end //ASSIGN_PORT_VC:
			
			WR_ON_FIFO:	begin 
				if(!cand_wr_vc_full) begin
					if(counter == 1) wr_flit_type		= 	HDR_FLIT	;
					if(pck_eq_counter) begin
						ns					=	IDEAL;
						wr_done_next	=	1'b1;
						wr_flit_type	= TAIL_FLIT;	
					end else begin //if counter
						flit_out_wr_next	= 1'b1;
						counter_increase	= 1'b1;
						rd_mem_en			= 1'b1;
					end 
				end
			end//WR_ON_FIFO
			
			
			WR_ON_RAM:	begin
				rd_no_pck_err_next= 1'b0;
				if(ififo_tail_flg) begin
					ns							=	IDEAL;
					rd_done_next			=	1'b1;
					cand_rd_vc_rst			=	1'b1;
				
				end 
				else begin //if ififo_tail_flg	 
					if(cand_rd_vc_not_empty ) begin
						ififo_rd_en				= 1'b1; 
						credit_out				=	cand_rd_vc;
						counter_increase		= 1'b1;
						
						if( pck_eq_counter ) begin
							rd_ovr_size_err_next	=	1'b1;
						
						end else begin// counter
							wr_mem_en_next	=	1'b1;
						
						end// else countert
					end// if(cand_rd_vc_not_empty )
				end// else ififo_tail_flg
			end
			
			PROG_WR	:	begin
				if(ififo_tail_flg) begin
						ns							=	IDEAL;
						rd_done_next			=	1'b1;
						cand_rd_vc_rst			=	1'b1;
				end 
				else begin //if ififo_tail_flg	 
					if(cand_rd_vc_not_empty ) begin
						ififo_rd_en				= 1'b1; 
						credit_out				= cand_rd_vc;
						if(! ififo_hdr_flg)  counter_increase		= 1'b1;
						wr_mem_en_next			= 1'b1;
					end// if(cand_prog_vc_not_empty )
				end// else ififo_tail_flg
			end//PROG_WR		
			
			default : ns=IDEAL;
		
		endcase
	end
	
	
		
	
 fifo_buffer #(
	.VC_NUM_PER_PORT			(VC_NUM_PER_PORT),
	.FLIT_WIDTH					(FLIT_WIDTH ),
	.BUFFER_NUM_PER_VC		(BUFFER_NUM_PER_VC),
	.ENABLE_MIN_DEPTH_OUT	(0) // if 1 then the VC with minimum depth is merged with vc_nearly_full as output
	
	
	)	
	the_ififo
	(
	.din						(flit_in),     // Data in
	.vc_num_wr				(flit_in_vc_num),//write vertual channel 	
	.wr_en					(flit_in_wr),   // Write enable
	.vc_num_rd				(cand_rd_vc),//read vertual channel 	
	.rd_en					(ififo_rd_en),   // Read the next word
	.dout						(ififo_dout),    // Data out
	.vc_nearly_full		(),
	.vc_not_empty			(ififo_vc_not_empty),
	
		
	.reset					(reset),
	.clk						(clk)
	);

	
	
	arbiter #(
		.ARBITER_WIDTH (VC_NUM_PER_PORT),
		.CHOISE			(1)  // 0 blind round-robin and 1 true round robin
)
rd_vc_arbiter
(	
	.clk			(clk), 
   .reset 		(reset), 
   .request	 	(rd_vc_arbiter_in), 
   .grant		(rd_vc_arbiter_out), 
   .anyGrant	()
);



 xy #(
	.PORT_NUM					(PORT_NUM),
	.X_NODE_NUM					(X_NODE_NUM),
	.Y_NODE_NUM					(Y_NODE_NUM),
	.SW_X_ADDR					(SW_X_ADDR),
	.SW_Y_ADDR					(SW_Y_ADDR)
	
	)
	th_xy_rout
	(
	.dest_x_node_in			(dest_x_addr),
	.dest_y_node_in			(dest_y_addr),
	.port_sel_out				(port_sel)// one extra bit will be removed by cross bar switch later
	);
	
	
	

endmodule






module niosii_ram #(
		parameter	DATA_WIDTH	=32,
		parameter 	ADDR_WIDTH	=15


)
(
                           // inputs:
                            address,
                            byteenable,
                            chipselect,
                            clk,
                            clken,
                            reset,
                            write,
                            writedata,

                           // outputs:
                            readdata
                         );


 


  output  [ 31: 0] readdata;
  input   [ ADDR_WIDTH-1: 0] address;
  input   [  3: 0] byteenable;
  input            chipselect;
  input            clk;
  input            clken;
  input            reset;
  input            write;
  input   [ 31: 0] writedata;

  wire    [ 31: 0] readdata;
  wire             wren;
  assign wren = chipselect & write;
 
dual_port_ram
#(
 .DATA_WIDTH		(DATA_WIDTH),
 .ADDR_WIDTH 		(ADDR_WIDTH-2)
 )
 the_nios_ram
(
	.clk		(clk),
	
	.data_a	(writedata),
	.addr_a	(address[ADDR_WIDTH-1:2]),
	.we_a		(wren),
	.q_a		(readdata),
	
	.data_b(),
	.addr_b(),
	.we_b(),
	.q_b()
	
	
);

 endmodule
 

